<!DOCTYPE html>
<html lang="zh-CN">

<!-- Head tag -->
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  
  <meta name="baidu-site-verification" content="undefined" />
  <meta name="google-site-verification" content="undefined" />
  <meta name="msvalidate.01" content="undefined" />
  <meta name="360-site-verification" content="undefined" />
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- if page has tags, then add tags to keyword -->
  
  
  <meta name="keywords" content=",Golang汇编,Golang底层,Golang,">
  <!-- page.description has higher priority -->
  <meta name="description" content="Golang汇编第三篇文章，自己动手写Golang汇编">
  <link rel="shortcut icon" href="/blog/img/logo.webp">
  <title>
    
    Golang汇编(三)-编写自己的汇编代码 | Evrn
    
  </title>

  <link rel="canonical" href="/blog/post/go-asm-my-asm.html">
  
<link rel="stylesheet" href="/blog/css/reset.css">

  
<link rel="stylesheet" href="/blog/css/layout.css">

  
<link rel="stylesheet" href="/blog/css/markdown.css">

  <!-- Pygments Highlight CSS -->
  
<link rel="stylesheet" href="/blog/css/highlight.css">

  
<link rel="stylesheet" href="/blog/css/geektutu.css">

  <!-- global function -->
  <script>
    window.globalAddScript = function (url, onload, onerror) {
      var s = document.createElement('script');
      s.src = url;
      onload && (s.onload = onload);
      onerror && (s.onerror = onerror);
      document.body.appendChild(s);
    }
    window.globalAddCss = function (url) {
      var s = document.createElement('link');
      s.rel = 'stylesheet';
      s.href = url;
      document.body.appendChild(s);
    }
    window.getPosition = function (ele) {
      var x = 0, y = 0;
      while (ele) {
        x += (ele.offsetLeft - ele.scrollLeft + ele.clientLeft);
        y += (ele.offsetTop - ele.scrollTop + ele.clientTop);
        ele = ele.offsetParent;
      }
      return { x: x, y: y };
    }
    window.getDom = function (str) { return document.querySelector(str) }
  </script>
  <!-- google ad -->
  
<meta name="generator" content="Hexo 4.2.0"></head>

<body>
    <header class="gkt-header col-xs-12 padding-0">
    <div id="gkt-nav" class="gkt-header-container">
        <a href="/blog/" class="gkt-header-title float-left">
            <img class="float-left" src="/blog/img/logo.webp" alt="">
            <span>Evrn</span>
        </a>
        <nav class="gkt-header-nav text-right">
            <ul>
                <li><a class="hidden-xs" href="/blog/">主页</a></li>
                <li><a href="/blog/series/">专题</a></li>
                <li><a href="/blog/archives/">归档</a></li>
                <li><a href="/blog/post/link.html">友链</a></li>
                <li><a href="/blog/post/about.html">留言</a></li>
            </ul>
        </nav>
    </div>
    <div id="gkt-cate-nav" class="gkt-header-container hidden-xs">
        
        <nav class="gkt-header-nav float-left">
            <ul>
                
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/go-learn-interface.html">Golang学习</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left active">
                    <a class="float-left" href="/blog/post/go-asm-indroduce.html">Golang汇编</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/tcp-three-way-handshake.html">基础</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/go-how-to-build.html">Golang底层</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/mysql-index-use.html">MySQL</a>
                    
                </li>
                
            </ul>
        </nav>
    </div>
</header>
<div style="height: 44px"></div>
<div class="hidden-xs" style="height: 44px"></div>
<script>
    (function () {
        window.addEventListener('scroll', function () {
            if (window.innerWidth < 768) {
                return;
            }
            var nav = document.querySelector('#gkt-nav');
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            scrollTop > 50 && (nav.classList.add('hide'));
            scrollTop <= 50 && (nav.classList.remove('hide'));
        });
        var cateNavs = document.querySelectorAll('#gkt-cate-nav>nav>ul>li');
        [].slice.call(cateNavs).forEach(function (item) {
            var sub = item.querySelector('.gkt-sub-cate');
            if (!sub) return;
            item.addEventListener('mouseenter', function (e) { sub.style.display = 'block'; }, false);
            item.addEventListener('mouseleave', function (e) { sub.style.display = 'none'; }, false);
        })
    })();
</script>
    <!-- Main Content -->
    <div class="main-container">
        <!-- Main Content -->
<main class="col-xs-12 padding-0 markdown-it">
    <!-- Post Container -->
    
    
    <!-- Post Content -->
<div class="float-left post-container box-shadow">
    <div class="u-arrow-wrapper hidden-xs">
        
        <a class="float-left" href="/blog/post/go-asm-method.html"><i class="u-arrow-left"></i></a>
        
        
        <a class="float-right" href="/blog/post/go-asm-datastruct.html"><i class="u-arrow-right"></i></a>
        
    </div>
    <article class="col-xs-12">
        <h1> Golang汇编(三)-编写自己的汇编代码 </h1>

        

        
        <div class="hidden-lg hidden-md series_links">
            <p> <strong> Golang汇编系列文章链接：</strong></p>
            <ul>
                
                <li>
                    <a href="/blog/post/go-asm-indroduce.html">Golang汇编(一)-Plan9介绍及指令详解</a>
                    <span class="post-item-date">(Feb 6, 2020)</span>
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-method.html">Golang汇编(二)-函数调用</a>
                    <span class="post-item-date">(Feb 16, 2020)</span>
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-my-asm.html">Golang汇编(三)-编写自己的汇编代码</a>
                    <span class="post-item-date">(Feb 19, 2020)</span>
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-datastruct.html">Golang汇编(四)-汇编眼中的数据结构</a>
                    <span class="post-item-date">(Feb 19, 2020)</span>
                </li>
                
            </ul>
        </div>
        
        
        
        <h2 id="go中调用汇编函数"><a href="#go中调用汇编函数" class="headerlink" title="go中调用汇编函数"></a>go中调用汇编函数</h2><p>首先在go文件中声明函数，不用实现</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//main.go</span></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">add</span><span class="params">(a, b <span class="keyword">int</span>)</span> <span class="title">int</span> // 汇编函数声明</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	<span class="built_in">println</span>(add(<span class="number">11</span>,<span class="number">22</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后在同一目录下新建一个main.s文件,实现函数体</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 因为我们声明函数用到了 NOSPLIT 这样的 flag，所以需要将 textflag.h 包含进来</span><br><span class="line">#include &quot;textflag.h&quot; </span><br><span class="line"></span><br><span class="line">TEXT ·add(SB),NOSPLIT,$0-8</span><br><span class="line">    MOVQ a+0(FP),AX  &#x2F;&#x2F;取参数1，a,放到AX寄存器</span><br><span class="line">    ADDQ b+8(FP),AX  &#x2F;&#x2F;将参数2，b与AX寄存器的值相加并存放回AX中</span><br><span class="line">    MOVQ AX, ret+16(FP)</span><br><span class="line">    RET</span><br></pre></td></tr></table></figure>

<p>然后运行</p>
<p><code>go build</code> 就可以得到可执行文件</p>
<p>输出33</p>
<h2 id="开始编写汇编代码"><a href="#开始编写汇编代码" class="headerlink" title="开始编写汇编代码"></a>开始编写汇编代码</h2><h3 id="参数返回值取值"><a href="#参数返回值取值" class="headerlink" title="参数返回值取值"></a>参数返回值取值</h3><p>回看之前说过的函数栈分布</p>
<p><img src="http://river.marvelthanos.com/blog/20200302/jPj06R6RE7AO.png?imageslim" alt="mark"></p>
<p>对于参数返回值的读取和控制主要使用<strong>伪FP</strong>来操作</p>
<p>对于局部变量和调用函数的参数主要使用<strong>伪SP</strong>来操作</p>
<p>注意，因为编译器会根据不同的情况决定是否插入callerBP, 伪 SP 和伪 FP 的相对位置是<strong>会变化的</strong>，有时候相差8字节，有时候相差16个字节，因此在手写时不应该用伪 SP 和 &gt;0 的 offset 来引用数据，否则结果可能会出乎你的预料。</p>
<h4 id="caller-BP到底是什么？"><a href="#caller-BP到底是什么？" class="headerlink" title="caller BP到底是什么？"></a>caller BP到底是什么？</h4><p>实际上就是上层调用函数(Parent)的BP寄存器的值也是上层caller的伪SP的地址</p>
<h4 id="什么时候会插入caller-BP"><a href="#什么时候会插入caller-BP" class="headerlink" title="什么时候会插入caller BP"></a>什么时候会插入caller BP</h4><p>caller BP 是<strong>编译期由编译器插入的</strong>，计算 frame size 时是不包括这个 caller BP 部分的。是否插入 caller BP 的主要判断依据是:</p>
<ol>
<li>函数的栈帧大小大于 0</li>
<li>下述函数返回 true</li>
</ol>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Go\src\cmd\internal\objabi\util.go</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Framepointer_enabled</span><span class="params">(goos, goarch <span class="keyword">string</span>)</span> <span class="title">bool</span></span> &#123;</span><br><span class="line">    <span class="keyword">return</span> framepointer_enabled != <span class="number">0</span> &amp;&amp; goarch == <span class="string">"amd64"</span> &amp;&amp; goos != <span class="string">"nacl"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>GOOS：目标平台的操作系统（darwin、freebsd、linux、windows）<br>GOARCH：目标平台的体系架构（386、amd64、arm）</p>
</blockquote>
<p>如果编译器在最终的汇编结果中没有插入 caller BP(源代码中所称的 frame pointer)的情况下，伪 SP 和伪 FP 之间只有 8 个字节的 caller 的 return address，而插入了 BP 的话，就会多出额外的 8 字节。也就说伪 SP 和伪 FP 的相对位置是不固定的，有可能是间隔 8 个字节，也有可能间隔 16 个字节。并且判断依据会<strong>根据平台</strong>和 <strong>Go 的版本</strong>有所不同。</p>
<blockquote>
<p>在linux,amd64环境下，经测试是否插入Caller BP只取决于栈帧是否大于0，至于framepointer_enabled受什么影响，还有待考究</p>
</blockquote>
<h3 id="argsize-和-framesize-计算规则"><a href="#argsize-和-framesize-计算规则" class="headerlink" title="argsize 和 framesize 计算规则"></a>argsize 和 framesize 计算规则</h3><h4 id="argsize"><a href="#argsize" class="headerlink" title="argsize"></a>argsize</h4><p>在函数声明中:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">TEXT pkgname·add(SB),NOSPLIT,$16-32</span><br></pre></td></tr></table></figure>

<p>前面已经说过 $16-32 表示 $framesize-argsize。Go 在函数调用时，参数和返回值都需要由 caller 在其栈帧上备好空间。callee 在声明时仍然需要知道这个 argsize。argsize 的计算方法是，参数大小求和+返回值大小求和，例如入参是 3 个 int64 类型，返回值是 1 个 int64 类型，那么这里的 argsize = sizeof(int64) * 4。</p>
<p>不过真实世界永远没有我们假设的这么美好，函数参数往往混合了多种类型，还需要考虑内存对齐问题。</p>
<p>如果不确定自己的函数签名需要多大的 argsize，可以通过简单实现一个相同签名的空函数，然后 go tool objdump 来逆向查找应该分配多少空间。</p>
<h4 id="framesize"><a href="#framesize" class="headerlink" title="framesize"></a>framesize</h4><p>函数的 framesize 就稍微复杂一些了，手写代码的 framesize 不需要考虑由编译器插入的 caller BP和return addr，要考虑：</p>
<ol>
<li>局部变量，及其每个变量的 size。</li>
<li>在函数中是否有对其它函数调用时，如果有的话，调用时需要将 callee 的参数、返回值考虑在内。虽然 return address(rip)的值也是存储在 caller 的 stack frame 上的，但是这个过程是由 CALL 指令和 RET 指令完成 PC 寄存器的保存和恢复的，在手写汇编时，同样也是不需要考虑这个 PC 寄存器在栈上所需占用的 8 个字节的。</li>
<li>原则上来说，调用函数时只要不把局部变量覆盖掉就可以了。稍微多分配几个字节的 framesize 也不会死，多分配的空间集中在中间</li>
<li>在确保逻辑没有问题的前提下，你愿意覆盖局部变量也没有问题。只要保证进入和退出汇编函数时的 caller 和 callee 能正确拿到返回值就可以。</li>
</ol>
<h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>以下案例都在linux，amd64，go 1.12环境下得出</p>
<h3 id="输出伪FP，伪SP的值"><a href="#输出伪FP，伪SP的值" class="headerlink" title="输出伪FP，伪SP的值"></a>输出伪FP，伪SP的值</h3><p>很多文档里面对于伪FP，伪SP，Caller BP，return address等这些名词都说的云里雾里，似是而非，这里就利用汇编直接输出这些值</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;函数声明 func SpFp() (int,int,int)  </span><br><span class="line">TEXT ·SpFp(SB),$0-24</span><br><span class="line">    LEAQ (SP), AX   &#x2F;&#x2F;没有symbol,指硬件Sp</span><br><span class="line">    LEAQ a+8(SP), BX &#x2F;&#x2F;有symbol,指伪Sp，向上偏移8字节</span><br><span class="line">    LEAQ b+0(FP), CX &#x2F;&#x2F;伪FP</span><br><span class="line">    MOVQ AX, ret+0(FP) &#x2F;&#x2F;返回值1</span><br><span class="line">    MOVQ BX, ret+8(FP)&#x2F;&#x2F;返回值2</span><br><span class="line">    MOVQ CX, ret+16(FP)&#x2F;&#x2F;返回值3</span><br><span class="line">    RET</span><br></pre></td></tr></table></figure>

<p>得到结果 <strong>硬件SP</strong>=<strong>伪SP</strong>=<strong>伪FP</strong>-8</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">824634244880 824634244888 824634244888</span><br><span class="line">  硬件SP值       伪SP+8       伪FP</span><br></pre></td></tr></table></figure>

<p>所以这里的函数栈是这样的，因为没有任何局部变量和调用函数framesize=0，argsize就是三个整形，3*8=24，所以framesize-argsize 就是 $0-24</p>
<p><img src="http://river.marvelthanos.com/blog/20200302/EWRNo1fUVUuj.png?imageslim" alt="mark"></p>
<p>之前说过在我的环境下，是否插入Caller BP只取决于栈帧是否大于0</p>
<p>我们修改一下程序</p>
<p>TEXT ·SpFp(SB),$0-24  = &gt; TEXT ·SpFp(SB),$16-24</p>
<p>得到结果</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">824634244856 824634244880 824634244888</span><br><span class="line">  硬件SP值       伪SP+8       伪FP</span><br></pre></td></tr></table></figure>

<p>此时，<strong>硬件SP</strong>=<strong>伪SP</strong>-16=<strong>伪FP</strong>-16-16</p>
<p>可以看到伪SP+8的距离伪FP的位置还有8个字节，因此这里编译器已经帮我们插入了Caller BP，这时候的函数栈是这样的</p>
<p><img src="http://river.marvelthanos.com/blog/20200302/pAqceVhqdyBj.png?imageslim" alt="mark"></p>
<h3 id="汇编调用函数"><a href="#汇编调用函数" class="headerlink" title="汇编调用函数"></a>汇编调用函数</h3><p>在汇编语言里面调用汇编和调用普通函数其实是一样的只要放置好对应的参数，调用call 指令即可。</p>
<p>假设现在要实现这个效果</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">yyy1</span><span class="params">(a, b, c <span class="keyword">int</span>)</span> [3]<span class="title">int</span></span> &#123;</span><br><span class="line">    <span class="keyword">return</span> zzz(a, b, c)</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">zzz</span><span class="params">(a, b, c <span class="keyword">int</span>)</span> [3]<span class="title">int</span></span>&#123;</span><br><span class="line">	<span class="keyword">var</span> d [<span class="number">3</span>]<span class="keyword">int</span></span><br><span class="line">	d[<span class="number">0</span>], d[<span class="number">1</span>], d[<span class="number">2</span>] = a, b, c</span><br><span class="line">	<span class="keyword">return</span> d</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    y1 := yyy1(<span class="number">11</span>, <span class="number">22</span>, <span class="number">33</span>)</span><br><span class="line">    <span class="built_in">println</span>(y1[<span class="number">0</span>], y1[<span class="number">1</span>], y1[<span class="number">2</span>])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>首先声明两个汇编函数,main.go</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">yyy1</span><span class="params">(a, b, c <span class="keyword">int</span>)</span> [3]<span class="title">int</span></span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">zzz</span><span class="params">(a, b, c <span class="keyword">int</span>)</span> [3]<span class="title">int</span></span></span><br></pre></td></tr></table></figure>

<p>然后编写汇编</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">#include &quot;textflag.h&quot; </span><br><span class="line">TEXT ·yyy1(SB),$48-48</span><br><span class="line">    MOVQ arg1+0(FP), AX</span><br><span class="line">    MOVQ AX, a-48(SP)    &#x2F;&#x2F;获取参数1</span><br><span class="line"></span><br><span class="line">    MOVQ arg2+8(FP), AX</span><br><span class="line">    MOVQ AX, b-40(SP)    &#x2F;&#x2F;获取参数2</span><br><span class="line"></span><br><span class="line">    MOVQ arg3+16(FP),AX</span><br><span class="line">    MOVQ AX,c-32(SP)    &#x2F;&#x2F;获取参数3</span><br><span class="line"></span><br><span class="line">    CALL ·zzz(SB)</span><br><span class="line">    MOVQ cret0-24(SP),BX</span><br><span class="line">    MOVQ BX,ret0+24(FP)     &#x2F;&#x2F;获取返回值1</span><br><span class="line"></span><br><span class="line">    MOVQ cret1-16(SP),BX</span><br><span class="line">    MOVQ BX,ret1+32(FP)    &#x2F;&#x2F;获取返回值2</span><br><span class="line"></span><br><span class="line">    MOVQ cret2-8(SP),BX</span><br><span class="line">    MOVQ BX,ret2+40(FP)    &#x2F;&#x2F;获取返回值3</span><br><span class="line"></span><br><span class="line">    RET</span><br><span class="line">    </span><br><span class="line">TEXT ·zzz(SB),NOSPLIT,$24-48</span><br><span class="line">    MOVQ a+0(FP),AX</span><br><span class="line">    MOVQ AX,d0-24(SP)</span><br><span class="line"></span><br><span class="line">    MOVQ a+8(FP),BX</span><br><span class="line">    MOVQ BX,d1-16(SP)</span><br><span class="line"></span><br><span class="line">    MOVQ a+16(FP),CX</span><br><span class="line">    MOVQ CX,d2-8(SP)</span><br><span class="line"></span><br><span class="line">    MOVQ d0-24(SP),CX</span><br><span class="line">    MOVQ CX,ret0+24(FP)</span><br><span class="line"></span><br><span class="line">    MOVQ d1-16(SP),CX</span><br><span class="line">    MOVQ CX,ret1+32(FP)</span><br><span class="line"></span><br><span class="line">    MOVQ d2-8(SP),CX</span><br><span class="line">    MOVQ CX,ret2+40(FP)</span><br><span class="line">    RET</span><br></pre></td></tr></table></figure>



<p>函数栈长这样</p>
<p><img src="http://river.marvelthanos.com/blog/20200302/Q7N8xfDcuvUH.png?imageslim" alt="mark"></p>
<p>之前说过callerBP是在进入函数后编译器自动加入的</p>
<p>我们通过<code>go tool objdump</code> 反编译最终程序的时候可以看到，多了维护SP的代码</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">TEXT main.zzz(SB) </span><br><span class="line">  main.s:77		0x48e8b0		4883ec20		SUBQ $0x20, SP		</span><br><span class="line">  main.s:77		0x48e8b4		48896c2418		MOVQ BP, 0x18(SP)	</span><br><span class="line">  main.s:77		0x48e8b9		488d6c2418		LEAQ 0x18(SP), BP	</span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<p>另外return addr是由编译器在调动call指令的时候自动插入的，因此在计算framesize的时候<strong>不用考虑存储 call bp和return addr的位置空间</strong></p>
<p>另外之前说过整个调用的过程其实都是同一个栈通过伸缩完成的，其实在zzz里面通过控制FP或者SP可以直接访问到，父函数的参数，如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">TEXT ·zzz(SB),NOSPLIT,$24-48</span><br><span class="line">    MOVQ a+0(FP),AX &#x3D;&gt; MOVQ a+64(FP),AX</span><br></pre></td></tr></table></figure>

<p>这样结果是一样的，当然不建议这么做，这里只做演示</p>
<h3 id="实现斐波那契"><a href="#实现斐波那契" class="headerlink" title="实现斐波那契"></a>实现斐波那契</h3><p>常见的斐波那契实现</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">fibo</span><span class="params">(n <span class="keyword">int</span>)</span> <span class="title">int</span></span> &#123;</span><br><span class="line">	<span class="keyword">if</span>  n &lt;= <span class="number">1</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> fibo(n<span class="number">-1</span>) + fibo(n<span class="number">-2</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>用汇编实现</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">TEXT ·fibo(SB),$16-16</span><br><span class="line">    MOVQ arg0+0(FP),AX</span><br><span class="line">    CMPQ AX,$1</span><br><span class="line">    JBE end</span><br><span class="line"></span><br><span class="line">    MOVQ AX,arg0-16(SP)</span><br><span class="line">    DECQ arg0-16(SP)</span><br><span class="line">    CALL ·fibo(SB)</span><br><span class="line">    MOVQ ret0-8(SP),BX</span><br><span class="line">    MOVQ BX,ret+8(FP)</span><br><span class="line"></span><br><span class="line">    DECQ arg0-16(SP)</span><br><span class="line">    CALL ·fibo(SB)</span><br><span class="line">    MOVQ ret0-8(SP),BX</span><br><span class="line">    ADDQ BX,ret+8(FP)</span><br><span class="line">    RET</span><br><span class="line">end:</span><br><span class="line">    MOVQ $1, ret+8(FP)</span><br><span class="line">    RET</span><br></pre></td></tr></table></figure>

<p>调用栈</p>
<p><img src="http://river.marvelthanos.com/blog/20200302/EAVpUAGnBI6Y.png?imageslim" alt="mark"></p>
<p>可以看出调用多个函数的时候其实是公用栈空间的，不需要重新分配</p>
<p>这里也可以看到，我没使用NOSPLIT，因为这是递归函数，函数栈可以能一直增长，肯定超过128个字节，这时就要编译器自动插入栈增长检测代码。</p>
<p>通过<code>go tool objdump</code> 可以看到，编译器为我们增加了以下代码j,用于栈扩容检测</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">main.s:97	0x48e789	MOVQ 0(CX), CX				</span><br><span class="line">main.s:97	0x48e790	CMPQ 0x10(CX), SP			</span><br><span class="line">main.s:97	0x48e794	JBE 0x48e7f6		</span><br><span class="line">...</span><br><span class="line">main.s:97	0x48e7f6	CALL runtime.morestack_noctxt(SB)	</span><br><span class="line">main.s:97	0x48e7fb	JMP main.fibo(SB)</span><br></pre></td></tr></table></figure>



<h3 id="获取-goroutine-id"><a href="#获取-goroutine-id" class="headerlink" title="获取 goroutine id"></a><a href="https://github.com/cch123/golang-notes/blob/master/assembly.md#获取-goroutine-id" target="_blank" rel="noopener">获取 goroutine id</a></h3><p>Go 的 goroutine 是一个叫 g 的结构体，内部有自己的唯一 id，不过 runtime 没有把这个 id 暴露出来，但不知道为什么有很多人就是想把这个 id 得到。于是就有了各种或其 goroutine id 的库。</p>
<p>在 struct 一小节我们已经提到，结构体本身就是一段连续的内存，我们知道起始地址和字段的偏移量的话，很容易就可以把这段数据搬运出来:</p>
<p>go_tls.h:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">#ifdef GOARCH_arm</span><br><span class="line">#define LR R14</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">#ifdef GOARCH_amd64</span><br><span class="line">#define    get_tls(r)    MOVQ TLS, r</span><br><span class="line">#define    g(r)    0(r)(TLS*1)</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">#ifdef GOARCH_amd64p32</span><br><span class="line">#define    get_tls(r)    MOVL TLS, r</span><br><span class="line">#define    g(r)    0(r)(TLS*1)</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">#ifdef GOARCH_386</span><br><span class="line">#define    get_tls(r)    MOVL TLS, r</span><br><span class="line">#define    g(r)    0(r)(TLS*1)</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure>

<p>goid.go:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> goroutineid</span><br><span class="line"><span class="keyword">import</span> <span class="string">"runtime"</span></span><br><span class="line"><span class="keyword">var</span> offsetDict = <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">int64</span>&#123;</span><br><span class="line">    <span class="comment">// ... 省略一些行</span></span><br><span class="line">    <span class="string">"go1.7"</span>:    <span class="number">192</span>,</span><br><span class="line">    <span class="string">"go1.7.1"</span>:  <span class="number">192</span>,</span><br><span class="line">    <span class="string">"go1.7.2"</span>:  <span class="number">192</span>,</span><br><span class="line">    <span class="string">"go1.7.3"</span>:  <span class="number">192</span>,</span><br><span class="line">    <span class="string">"go1.7.4"</span>:  <span class="number">192</span>,</span><br><span class="line">    <span class="string">"go1.7.5"</span>:  <span class="number">192</span>,</span><br><span class="line">    <span class="string">"go1.7.6"</span>:  <span class="number">192</span>,</span><br><span class="line">    <span class="comment">// ... 省略一些行</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> offset = offsetDict[runtime.Version()]</span><br><span class="line"></span><br><span class="line"><span class="comment">// GetGoID returns the goroutine id</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GetGoID</span><span class="params">()</span> <span class="title">int64</span></span> &#123;</span><br><span class="line">    <span class="keyword">return</span> getGoID(offset)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getGoID</span><span class="params">(off <span class="keyword">int64</span>)</span> <span class="title">int64</span></span></span><br></pre></td></tr></table></figure>

<p>goid.s:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">#include &quot;textflag.h&quot;</span><br><span class="line">#include &quot;go_tls.h&quot;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; func getGoID() int64</span><br><span class="line">TEXT ·getGoID(SB), NOSPLIT, $0-16</span><br><span class="line">    get_tls(CX)</span><br><span class="line">    MOVQ g(CX), AX</span><br><span class="line">    MOVQ offset(FP), BX</span><br><span class="line">    LEAQ 0(AX)(BX*1), DX</span><br><span class="line">    MOVQ (DX), AX</span><br><span class="line">    MOVQ AX, ret+8(FP)</span><br><span class="line">    RET</span><br></pre></td></tr></table></figure>

<p>这样就实现了一个简单的获取 struct g 中的 goid 字段的小 library，作为玩具放在这里:</p>
<blockquote>
<p><a href="https://github.com/cch123/goroutineid" target="_blank" rel="noopener">https://github.com/cch123/goroutineid</a></p>
</blockquote>
<h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><p><a href="club.huawei.com/forum-4333-1.html">golang内核系列–深入理解plan9汇编&amp;实践</a></p>
<p><a href="https://github.com/cch123/golang-notes/blob/master/assembly.md#map" target="_blank" rel="noopener">plan9 assembly 完全解析</a></p>
<p><a href="https://lrita.github.io/2017/12/12/golang-asm/" target="_blank" rel="noopener">golang 汇编</a></p>

        
<script src="/blog/js/qrious.min.js"></script>


        <hr>
        <div>
            
            <p>
                <span>专题: </span>
                
                <a href="/blog/series/#Golang汇编">
                    <code key="Golang汇编" class="post-label">Golang汇编</code>
                </a>
                
            </p>
            
            <p>
                <span>本文发表于 2020-02-19，最后修改于 2020-03-13。</span>
            </p>
            <!-- 文章末尾的提示 start -->
            
            
            
                
            <!-- 文章末尾的提示 end -->
        </div>
        <hr />
        <p name="pagination" style="font-size: 1.2em">
    
    <a class="float-left" href="/blog/post/go-asm-method.html">上一篇 « Golang汇编(二)-函数调用</a>
    
    
    <a class="float-right" href="/blog/post/go-asm-datastruct.html">下一篇 » Golang汇编(四)-汇编眼中的数据结构</a>
    
</p>
    </article>
    <!-- 赞赏 -->
    <!--打赏-->

    <!-- 推荐阅读三篇文章 -->
    <div class="col-xs-12">
        <h3>推荐阅读</h3>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/go.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#Golang"
                    title="Golang">Golang</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#Golang源码"
                    title="Golang源码">Golang源码</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/go-learn-lib-sorts.html" class="title">
                Go源码学习 sort包
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2020-03-11，</span>
                    <span class="hidden-xs">全文8862字，</span>
                    <span>阅读约30分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/database/database.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#MySQL"
                    title="MySQL">MySQL</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#MySQL优化"
                    title="MySQL优化">MySQL优化</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/mysql-optimize-explain.html" class="title">
                EXPLAIN-执行计划查询命令
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2020-03-03，</span>
                    <span class="hidden-xs">全文11409字，</span>
                    <span>阅读约39分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/go.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#Golang底层"
                    title="Golang底层">Golang底层</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#Golang"
                    title="Golang">Golang</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/go-how-to-build.html" class="title">
                Golang程序编译执行流程
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2020-03-01，</span>
                    <span class="hidden-xs">全文7487字，</span>
                    <span>阅读约25分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
    </div>
    <div class="col-xs-12">
        <!-- 标签列表 -->
        <!-- Featured Tags -->
<style>
    #featured-tag .post-tag-item {
        font-size: 12px;
        line-height: 30px;
        display: inline-block;
        height: 30px;
        margin: 5px 0px;
        padding: 0 7px;
        color: #333;
        border-radius: 15px;
        background: #f6f6f6;
    }

    #featured-tag .post-tag-item:hover {
        color: #337ab7;
    }
</style>
<div id="featured-tag">
    
    <a class="post-tag-item" href="/blog/tags/#关于我" title="关于我"
        rel="1">#关于我 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#友链" title="友链"
        rel="1">#友链 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang汇编" title="Golang汇编"
        rel="4">#Golang汇编 (4) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang底层" title="Golang底层"
        rel="5">#Golang底层 (5) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang" title="Golang"
        rel="8">#Golang (8) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang基础" title="Golang基础"
        rel="1">#Golang基础 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang源码" title="Golang源码"
        rel="2">#Golang源码 (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#基础" title="基础"
        rel="2">#基础 (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#tcp/http" title="tcp/http"
        rel="2">#tcp/http (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#MySQL" title="MySQL"
        rel="3">#MySQL (3) </a>
    
    <a class="post-tag-item" href="/blog/tags/#MySQL优化" title="MySQL优化"
        rel="1">#MySQL优化 (1) </a>
    
</div>
    </div>
    <!-- 评论 -->
    <div class="col-xs-12">
        
    </div>
</div>
<aside class="float-left gkt-sidebar hidden-xs hidden-sm">
    <div style="clear: both"></div>
    <div class="gkt-sidebar-wrapper">
        <section class="box-shadow"><style>
    .gkt-summary {
        border: 1px solid #DDDDDD;
        border-radius: 3px;
        padding: 5px;
        width: 100%;
    }


    .gkt-summary nav {
        overflow: hidden;
    }

    .gkt-summary nav a {
        display: inline-block;
        text-align: center;
        color: #333;
        font-size: 12px;
    }

    .gkt-summary nav span {
        display: block;
    }

    .gkt-summary nav .middle {
        border-left: 1px solid #eaecef;
        border-right: 1px solid #eaecef;
    }

    .gkt-summary .number {
        font-weight: bold;
    }

    .gkt-summary .link-list {
        margin-top: 5px;
        margin-bottom: -5px;
        padding-top: 7px;
        border-top: 1px dashed #999;
        display: flex;
    }

    .gkt-summary .link-list a {
        flex: 1;
    }

    .gkt-summary .link-list img {
        width: 25px;
        height: 25px;
    }
</style>

<div class="gkt-summary">
    <nav>
        <a href="/blog/" class="col-xs-4">
            <number class="number">15</number><span>文章</span>
        </a>
        <a href="/blog/series" class="col-xs-4 middle">
            <number class="number">5</number><span>专题</span>
        </a>
        <a href="/blog/tags" class="col-xs-4">
            <number class="number">11</number><span>标签</span>
        </a>
    </nav>

    
    <div class="link-list">
        
        <a href="https://marvelthanos.com" target="_blank" rel="noopener"><img src="/blog/img/related_links/thanos.webp" /></a>
        
    </div>
    
</div></section>
        
        
        <section class="gkt-sidebar-content box-shadow">
            <strong>Golang汇编</strong>
            <ul>
                
                <li>
                    <a href="/blog/post/go-asm-indroduce.html"
                        class="">Golang汇编(一)-Plan9介绍及指令详解</a>
                    
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-method.html"
                        class="">Golang汇编(二)-函数调用</a>
                    
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-my-asm.html"
                        class="gkt-sidebar-active">Golang汇编(三)-编写自己的汇编代码</a>
                    
                    <!-- Table of Contents -->
<div id="sidebar-toc">
  <!-- TOC  -->
  
  <ol class="toc-nav"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#go中调用汇编函数"><span class="toc-nav-text">go中调用汇编函数</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#开始编写汇编代码"><span class="toc-nav-text">开始编写汇编代码</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#实例"><span class="toc-nav-text">实例</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#参考文章"><span class="toc-nav-text">参考文章</span></a></li></ol>
  
</div>

<script>
  (function () {
    var h2 = document.querySelectorAll('article h2');
    var h3 = document.querySelectorAll('article h3');
    var linkList = document.querySelectorAll('#sidebar-toc a');

    function findLinkElement(name) {
      for (var i = 0; i < linkList.length; i++) {
        var items = linkList[i].href.split('#');
        if (items && items[items.length - 1] === encodeURIComponent(name)) {
          return i;
        }
      }
      return -1;
    }

    function activeLink(titleList) {
      var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      for (var i = titleList.length - 1; i >= 0; i--) {
        if (scrollTop - titleList[i].offsetTop > 0) {
          var index = findLinkElement(titleList[i].id);
          index != -1 && linkList[index].classList.add('gkt-sidebar-active');
          break;
        }
      }
    }

    window.addEventListener("scroll", function (e) {
      [].slice.call(linkList).forEach(function (link) {
        link.classList.remove('gkt-sidebar-active');
      })
      activeLink(h2);
    })
  })();
</script>
                    
                </li>
                
                <li>
                    <a href="/blog/post/go-asm-datastruct.html"
                        class="">Golang汇编(四)-汇编眼中的数据结构</a>
                    
                </li>
                
            </ul>
        </section>
        
        
        <section class="box-shadow">
            <strong>最近的文章</strong>
            <ul>
                
                <li>
                    <a href="/blog/post/go-learn-lib-sorts.html">Go源码学习 sort包</a>
                </li>
                
                <li>
                    <a href="/blog/post/mysql-optimize-explain.html">EXPLAIN-执行计划查询命令</a>
                </li>
                
                <li>
                    <a href="/blog/post/mysql-index-base.html">《高性能MySQL》笔记-索引基础</a>
                </li>
                
            </ul>
        </section>
        <section class="box-shadow text-center">
            
        </section>
    </div>
</aside>

<script>
    (function () {
        var ele = getDom('.gkt-sidebar-content')
        var wrapper = getDom('.gkt-sidebar-wrapper')
        var last = 0
        window.addEventListener("scroll", function (e) {
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            var isDown = scrollTop > last;
            var pos = getPosition(ele).y - scrollTop;
            var downLimit = 50;
            var upLimit = -100;
            // uarrow.style.marginTop = scrollTop + 'px';
            isDown && pos <= downLimit && wrapper.classList.add("gkt-sidebar-fixed");
            !isDown && pos > upLimit && wrapper.classList.remove("gkt-sidebar-fixed");
            last = scrollTop
        })
    })();
    (function () {
        function resizeUArrow() {
            var s = getDom('.u-arrow-wrapper').style
            var pc = getDom('.post-container')
            s.left = getPosition(pc).x + 'px';
            s.width = pc.clientWidth + 'px';
        }
        resizeUArrow()
        window.addEventListener('resize', resizeUArrow);
    })();
</script>

    
</main>
    </div>
    <style>
    img#go-top {
        position: fixed;
        bottom: 100px;
        width: 50px;
        cursor: pointer;
        z-index: 9999;
    }
</style>
<img id="go-top" src="/blog/icon/top.png" class="hidden-xs" style="display: none" />
<script>
    (function () {
        var goTop = document.getElementById('go-top');
        var mainContainer = document.querySelector('.main-container');
        
        goTop.addEventListener('click', function () {
            window.scroll(0, 0);
        }, false);
        window.addEventListener('scroll', function () {
            var right = document.body.offsetWidth - mainContainer.getBoundingClientRect().right;
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            goTop.style.right = right + 10 + 'px'
            scrollTop > 700 && (goTop.style.display = "block");
            scrollTop <= 700 && (goTop.style.display = "none");
        });
    })();
</script>
    <style>
    #geektutu-click-img-container {
        position: fixed;
        left: 0;
        top: 0;
        text-align: center;
        width: 100%;
        display: none;
        z-index: 9999;
    }

    #geektutu-click-img-container img {
        object-fit: contain;
        background: #eaecef;
        padding: 15px;
        border-radius: 10px;
        height: auto;
        width: auto;
        vertical-align: middle;
    }
</style>


<div id="geektutu-click-img-container">
    <img src="" alt="Big Image">
</div>

<script>
    (function () {
        var container = document.querySelector('#geektutu-click-img-container')
        var targetImg = container.querySelector('img')
        var imgs = document.querySelectorAll('article img');
        targetImg.addEventListener('click', function (e) {
            container.style.display = 'none';
            e && e.stopPropagation && e.stopPropagation();
        }, false);

        for (var i = 0; i < imgs.length; ++i) {
            var img = imgs[i];
            img.addEventListener('click', (function (src, rate) {
                return function (e) {
                    e && e.stopPropagation && e.stopPropagation();
                    if (window.innerWidth < 980) {
                        return
                    }
                    targetImg.style.height = targetImg.style.width = 'auto';
                    if (window.innerWidth / window.innerHeight > rate) {
                        targetImg.style.height = (window.innerHeight - 20) + 'px';
                    } else {
                        targetImg.style.width = (window.innerWidth - 20) + 'px';
                    }
                    container.style.height = window.innerHeight + 'px'
                    container.style.lineHeight = window.innerHeight + 'px'
                    container.style.display = 'block';
                    targetImg.src = src;
                };
            }(img.src, img.width / img.height)), false)
        }
    })();
</script>
    <!-- Footer -->
    <!-- Footer -->
<style>
    footer {
        width: 100%;
        line-height: 1.5;
        padding: 20px;
    }

    footer a {
        color: #333;
        text-decoration: none;
    }

    .footer-hexo img {
        height: 20px;
        margin-bottom: -5px;
    }

    .footer-hexo a {
        color: #337ab7;
    }
</style>
<footer class="text-center col-xs-12">
    <p>
        <small>© 2020 - Evrn - </small>
        <small>
            <a target="_blank" rel="nofollow noopener" href="http://www.miitbeian.gov.cn/"></a>
        </small>
    </p>
    <p class="footer-hexo">
        <!-- 但若直接使用或修改主题，请务必保留这段声明 -->
        <small>Powered by <a target="_blank" href="https://hexo.io">Hexo</a> | Theme
            <a target="_blank" href="https://geektutu.com">Geektutu</a>
            <a href="https://github.com/geektutu/hexo-theme-geektutu" target="_blank" rel="noopener">
                <img src=""
                    alt="Github Star">
            </a>
        </small>
    </p>
    
    <p>
        <small>
            <span id="busuanzi_container_site_pv">👁<span id="busuanzi_value_site_pv"></span></span> &nbsp;
            <span id="busuanzi_container_page_pv">📚<span id="busuanzi_value_page_pv"></span></span>
        </small>
    </p>
    
    

</footer>


<script>
    window.addEventListener('load', function () {
        globalAddScript('//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js');
    })
</script>

<!-- Baidu Tongji -->





    <script>
        // 非本站网址，新开tab页签
        (function () {
            var stopBubble = function (e) {
                e && e.stopPropagation && e.stopPropagation();
            }
            var links = document.querySelectorAll('a');
            [].slice.call(links).forEach(function (item) {
                if (item.href && item.href.indexOf(window.location.host) === -1) {
                    item.target = '_blank'
                }
                // 阻止冒泡，不触发彩蛋。
                item.addEventListener('click', stopBubble, false);
            });
            var article = document.querySelector('article');
            article && article.addEventListener('click', stopBubble, false)
        })();
    </script>
    
</body>

</html>